import { GlCard, GlSkeletonLoader } from '@gitlab/ui';
import Vue, { nextTick } from 'vue';

import VueApollo from 'vue-apollo';
import VulnerabilityCounts from 'ee/security_dashboard/components/shared/vulnerability_report/vulnerability_counts.vue';
import { mountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import { DASHBOARD_TYPES, SEVERITY_LEVELS } from 'ee/security_dashboard/store/constants';
import { createAlert } from '~/alert';
import createMockApollo from 'helpers/mock_apollo_helper';
import countsQuery from 'ee/security_dashboard/graphql/queries/vulnerability_severities_count.query.graphql';
import { SEVERITIES } from '~/vulnerabilities/constants';

jest.mock('~/alert');

Vue.use(VueApollo);

const fullPath = 'path';
const counts = { critical: 1, high: 2, medium: 5, low: 4, info: 3, unknown: 6 };

const getCountsRequestHandler = ({
  data = counts,
  dashboardType = DASHBOARD_TYPES.PROJECT,
} = {}) => {
  return jest.fn().mockResolvedValue({
    data: {
      [dashboardType]: {
        id: '1',
        vulnerabilitySeveritiesCount: { __typename: 'VulnerabilitySeveritiesCount', ...data },
      },
    },
  });
};

const defaultCountsRequestHandler = getCountsRequestHandler();

describe('Vulnerability counts component', () => {
  let wrapper;

  const createWrapper = ({
    dashboardType = DASHBOARD_TYPES.PROJECT,
    filters = {},
    countsHandler = defaultCountsRequestHandler,
  } = {}) => {
    wrapper = mountExtended(VulnerabilityCounts, {
      apolloProvider: createMockApollo([[countsQuery, countsHandler]]),
      provide: {
        fullPath,
        dashboardType,
      },
      propsData: { filters },
    });
  };

  const findCards = () => wrapper.findAllComponents(GlCard);
  const findCardWithSeverity = (severity) => wrapper.findByTestId(severity);

  describe('vulnerability counts query', () => {
    it('calls the query once with the expected data', () => {
      const filters = { a: 1, b: 2 };
      createWrapper({ filters });

      expect(defaultCountsRequestHandler).toHaveBeenCalledTimes(1);
      expect(defaultCountsRequestHandler).toHaveBeenCalledWith(
        expect.objectContaining({ ...filters, fullPath }),
      );
    });

    it('does not call the query if filters are not ready', () => {
      createWrapper({ filters: null });

      expect(defaultCountsRequestHandler).not.toHaveBeenCalled();
    });

    it('emits a count-changed event when the severity counts change', async () => {
      createWrapper({ filters: { a: 1, b: 2 } });
      await waitForPromises();
      expect(wrapper.emitted('counts-changed')[0][0]).toEqual(
        Object.entries(counts).map(([severity, count]) => ({
          severity,
          count,
        })),
      );
    });

    it('shows an error message if the query fails', async () => {
      const countsHandler = jest.fn().mockRejectedValue(new Error());
      createWrapper({ countsHandler });
      await waitForPromises();

      expect(createAlert).toHaveBeenCalled();
    });

    it.each([DASHBOARD_TYPES.PROJECT, DASHBOARD_TYPES.GROUP, DASHBOARD_TYPES.INSTANCE])(
      'sets the correct variable for the %s dashboard',
      async (dashboardType) => {
        createWrapper({ dashboardType });
        await nextTick();

        expect(defaultCountsRequestHandler).toHaveBeenCalledWith(
          expect.objectContaining({
            isProject: dashboardType === DASHBOARD_TYPES.PROJECT,
            isGroup: dashboardType === DASHBOARD_TYPES.GROUP,
            isInstance: dashboardType === DASHBOARD_TYPES.INSTANCE,
          }),
        );
      },
    );
  });

  it('shows a skeleton loading component for each count when the query is loading', () => {
    createWrapper();

    findCards().wrappers.forEach((card) => {
      expect(card.findComponent(GlSkeletonLoader).exists()).toBe(true);
    });
  });

  it('shows a skeleton loading component for each count when there are no filters', () => {
    createWrapper({ filters: null });

    findCards().wrappers.forEach((card) => {
      expect(card.findComponent(GlSkeletonLoader).exists()).toBe(true);
    });
  });

  it('should show a card for each severity with the correct count', async () => {
    createWrapper();
    await waitForPromises();

    // Check that there are exactly the same number of cards as there are severities.
    expect(findCards()).toHaveLength(Object.keys(counts).length);

    Object.entries(counts).forEach(([severity, count]) => {
      const cardText = findCardWithSeverity(severity).text();

      expect(cardText).toContain(SEVERITY_LEVELS[severity]);
      expect(cardText).toContain(count.toString());
    });
  });

  it('should show zero for the count when there is no value for that severity', async () => {
    const handler = getCountsRequestHandler({ data: {} });
    createWrapper({ countsHandler: handler });
    await waitForPromises();

    SEVERITIES.forEach((severity) => {
      expect(findCardWithSeverity(severity).text()).toContain('0');
    });
  });
});
